import pandas as pd
import os
import sys
import chardet
import xml.etree.ElementTree as ET
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox

# --- 数据处理函数 (与之前版本类似) ---
def read_csv_with_chardet(file_path):
    try:
        with open(file_path, 'rb') as f:
            detected_encoding = chardet.detect(f.read())['encoding']
        return pd.read_csv(file_path, encoding=detected_encoding) if detected_encoding else pd.read_csv(file_path)
    except Exception: raise UnicodeError(f"文件 {os.path.basename(file_path)} 的编码格式无法被自动识别。")

def read_xml_to_dataframe(file_path):
    try:
        tree = ET.parse(file_path)
        root = tree.getroot()
        data_list = [
            {cell.find('key').text: cell.find('value').text 
             for cell in row.findall('cell') 
             if cell.find('key') is not None and cell.find('value') is not None}
            for row in root.findall('.//table/row')
        ]
        return pd.DataFrame(data_list)
    except Exception as e: raise Exception(f"处理XML文件 '{os.path.basename(file_path)}' 时出错: {e}")

# --- 辅助函数部分 (show_feedback_and_exit, get_application_path, etc.) 保持不变 ---
# ...

# --- 主应用程序类 (完全替换) ---
class DataToolApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("通用数据聚合工具 v9.3 (稳定版)")
        self.geometry("800x600")
        
        self.app_path = self.get_application_path()
        self.config_path = os.path.join(self.app_path, 'config.json')
        
        # 定义默认/初始配置
        self.data_sources_config = {
            "FB": {"path": "", "type": "xlsx", "search_col": "广告系列名称"},
            "GG": {"path": "", "type": "xml", "search_col": "广告系列"},
            "Backend": {"path": "", "type": "csv", "search_col": "campaign_network"}
        }
        
        self.create_widgets()
        self.load_config() # 加载配置并刷新UI
        
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def get_application_path(self):
        if getattr(sys, 'frozen', False): return os.path.dirname(sys.executable)
        else: return os.path.dirname(os.path.abspath(__file__))

    def create_widgets(self):
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        notebook = ttk.Notebook(main_frame)
        notebook.pack(fill=tk.BOTH, expand=True, pady=5)

        self.tab_files = ttk.Frame(notebook, padding="10")
        notebook.add(self.tab_files, text='文件配置')
        self.create_file_config_tab() # 静态创建UI

        self.tab_tasks = ttk.Frame(notebook, padding="10")
        notebook.add(self.tab_tasks, text='任务列表')
        self.create_tasks_tab()
        
        self.tab_mappings = ttk.Frame(notebook, padding="10")
        notebook.add(self.tab_mappings, text='字段映射')
        self.create_mappings_tab()

        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill=tk.X, pady=10)
        self.run_button = ttk.Button(control_frame, text="开始处理", command=self.run_processing)
        self.run_button.pack(side=tk.RIGHT, padx=5)
        self.save_button = ttk.Button(control_frame, text="保存配置", command=lambda: self.save_config(show_success=True))
        self.save_button.pack(side=tk.RIGHT, padx=5)
        self.export_button = ttk.Button(control_frame, text="导出配置...", command=self.export_config)
        self.export_button.pack(side=tk.LEFT, padx=5)
        self.import_button = ttk.Button(control_frame, text="导入配置...", command=self.import_config)
        self.import_button.pack(side=tk.LEFT)
        
        self.status_var = tk.StringVar(value="准备就绪。")
        status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(fill=tk.X, side=tk.BOTTOM)

    def create_file_config_tab(self):
        """静态创建文件配置UI，基于初始配置的键"""
        self.file_path_vars = {}
        for name, config in self.data_sources_config.items():
            frame = ttk.LabelFrame(self.tab_files, text=f"{name} 数据源 ({config.get('type', 'N/A')})", padding="10")
            frame.pack(fill=tk.X, pady=5)
            self.file_path_vars[name] = tk.StringVar()
            ttk.Label(frame, text="文件路径:").pack(side=tk.LEFT, padx=5)
            ttk.Entry(frame, textvariable=self.file_path_vars[name]).pack(side=tk.LEFT, fill=tk.X, expand=True)
            ttk.Button(frame, text="浏览...", command=lambda n=name: self.browse_file(n)).pack(side=tk.LEFT, padx=5)

    def browse_file(self, source_name):
        file_type = self.data_sources_config.get(source_name, {}).get('type', '*')
        file_types = [(f"{file_type.upper()} files", f"*.{file_type}"), ("All files", "*.*")]
        filepath = filedialog.askopenfilename(title=f"选择 {source_name} 文件", filetypes=file_types)
        if filepath: self.file_path_vars[source_name].set(filepath)

    def create_tasks_tab(self):
        # ... (此方法无需改动) ...
        frame = ttk.Frame(self.tab_tasks); frame.pack(fill=tk.BOTH, expand=True)
        input_frame = ttk.LabelFrame(frame, text="新增任务", padding="5"); input_frame.pack(fill=tk.X, pady=5)
        ttk.Label(input_frame, text="平台:").grid(row=0, column=0, padx=5, pady=2, sticky=tk.W)
        self.task_platform = ttk.Combobox(input_frame, values=["FB", "GG"]); self.task_platform.grid(row=0, column=1, padx=5, pady=2, sticky=tk.EW)
        ttk.Label(input_frame, text="渠道:").grid(row=0, column=2, padx=5, pady=2, sticky=tk.W)
        self.task_channel = ttk.Entry(input_frame); self.task_channel.grid(row=0, column=3, padx=5, pady=2, sticky=tk.EW)
        ttk.Label(input_frame, text="关键字:").grid(row=0, column=4, padx=5, pady=2, sticky=tk.W)
        self.task_keyword = ttk.Entry(input_frame); self.task_keyword.grid(row=0, column=5, padx=5, pady=2, sticky=tk.EW)
        ttk.Button(input_frame, text="添加", command=self.add_task).grid(row=0, column=6, padx=10, pady=2)
        ttk.Button(input_frame, text="删除选中", command=self.remove_task).grid(row=0, column=7, padx=5, pady=2)
        input_frame.columnconfigure(1, weight=1); input_frame.columnconfigure(3, weight=1); input_frame.columnconfigure(5, weight=1)
        cols = ("平台", "渠道", "关键字"); self.task_tree = ttk.Treeview(frame, columns=cols, show='headings', height=5)
        for col in cols: self.task_tree.heading(col, text=col); self.task_tree.column(col, width=150)
        self.task_tree.pack(fill=tk.BOTH, expand=True)
        
    def add_task(self):
        platform = self.task_platform.get(); channel = self.task_channel.get(); keyword = self.task_keyword.get()
        if platform and channel and keyword: self.task_tree.insert("", "end", values=(platform, channel, keyword)); self.task_channel.delete(0, tk.END); self.task_keyword.delete(0, tk.END)

    def remove_task(self):
        for item in self.task_tree.selection(): self.task_tree.delete(item)

    def create_mappings_tab(self):
        frame = ttk.Frame(self.tab_mappings); frame.pack(fill=tk.BOTH, expand=True)
        input_frame = ttk.LabelFrame(frame, text="新增映射", padding="5"); input_frame.pack(fill=tk.X, pady=5)
        ttk.Label(input_frame, text="输出列名:").grid(row=0, column=0, padx=5, pady=2, sticky=tk.W)
        self.map_output = ttk.Entry(input_frame); self.map_output.grid(row=0, column=1, padx=5, pady=2, sticky=tk.EW)
        ttk.Label(input_frame, text="来源名称:").grid(row=0, column=2, padx=5, pady=2, sticky=tk.W)
        self.map_source_name = ttk.Combobox(input_frame, values=list(self.data_sources_config.keys())); self.map_source_name.grid(row=0, column=3, padx=5, pady=2, sticky=tk.EW)
        ttk.Label(input_frame, text="来源列名:").grid(row=0, column=4, padx=5, pady=2, sticky=tk.W)
        self.map_source_col = ttk.Entry(input_frame); self.map_source_col.grid(row=0, column=5, padx=5, pady=2, sticky=tk.EW)
        ttk.Button(input_frame, text="添加", command=self.add_mapping).grid(row=0, column=6, padx=10, pady=2)
        ttk.Button(input_frame, text="删除选中", command=self.remove_mapping).grid(row=0, column=7, padx=5, pady=2)
        input_frame.columnconfigure(1, weight=1); input_frame.columnconfigure(3, weight=1); input_frame.columnconfigure(5, weight=1)
        cols = ("输出列名", "来源名称", "来源列名"); self.map_tree = ttk.Treeview(frame, columns=cols, show='headings', height=5)
        for col in cols: self.map_tree.heading(col, text=col); self.map_tree.column(col, width=150)
        self.map_tree.pack(fill=tk.BOTH, expand=True)

    def add_mapping(self):
        output = self.map_output.get(); source_name = self.map_source_name.get(); source_col = self.map_source_col.get()
        if output and source_name and source_col: self.map_tree.insert("", "end", values=(output, source_name, source_col)); self.map_output.delete(0, tk.END); self.map_source_col.delete(0, tk.END)

    def remove_mapping(self):
        for item in self.map_tree.selection(): self.map_tree.delete(item)

    # --- 配置管理方法 (已重构和修正) ---
    def collect_config_from_ui(self):
        """核心修正：从内部配置 self.data_sources_config 开始，只更新路径"""
        # 深拷贝以避免修改原始配置
        import copy
        updated_sources = copy.deepcopy(self.data_sources_config)
        
        for name, var in self.file_path_vars.items():
            if name in updated_sources:
                updated_sources[name]['path'] = var.get()
        
        return {
            "data_sources": updated_sources,
            "tasks": [self.task_tree.item(child)['values'] for child in self.task_tree.get_children()],
            "column_mappings": [self.map_tree.item(child)['values'] for child in self.map_tree.get_children()]
        }

    def save_config(self, show_success=True):
        try:
            config_data = self.collect_config_from_ui()
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=4, ensure_ascii=False)
            if show_success: self.status_var.set("配置已保存。")
        except Exception as e:
            if show_success: messagebox.showerror("保存失败", f"无法保存 'config.json': {e}")

    def load_config(self):
        """核心修正：加载配置并刷新UI"""
        if not os.path.exists(self.config_path):
            self.status_var.set("未找到配置文件，请手动配置。")
            self.refresh_ui_from_config() # 即使没有文件，也用默认配置刷新UI
            return
        try:
            with open(self.config_path, 'r', encoding='utf-8') as f:
                config_data = json.load(f)
            # 核心：用加载的数据更新内部“大脑”
            self.data_sources_config = config_data.get('data_sources', self.data_sources_config)
            self.refresh_ui_from_config(config_data)
            self.status_var.set("配置已成功加载。")
        except Exception as e:
            messagebox.showerror("加载失败", f"加载 'config.json' 时出错: {e}")
            self.status_var.set("加载配置失败。")
            
    def refresh_ui_from_config(self, config=None):
        """使用内部配置或传入的配置数据来刷新整个UI"""
        if config is None: config = {}
        
        # 刷新文件路径
        for name, var in self.file_path_vars.items():
            path = self.data_sources_config.get(name, {}).get('path', '')
            var.set(path)

        # 清空和刷新列表
        for item in self.task_tree.get_children(): self.task_tree.delete(item)
        for task in config.get('tasks', []):
            if len(task) == 3: self.task_tree.insert("", "end", values=task)

        for item in self.map_tree.get_children(): self.map_tree.delete(item)
        for mapping in config.get('column_mappings', []):
            if len(mapping) == 3: self.map_tree.insert("", "end", values=mapping)

    def export_config(self):
        filepath = filedialog.asksaveasfilename(title="导出配置到...", defaultextension=".json", filetypes=[("JSON files", "*.json")], initialfile="tool_config.json")
        if not filepath: return
        try:
            config_data = self.collect_config_from_ui()
            with open(filepath, 'w', encoding='utf-8') as f: json.dump(config_data, f, indent=4, ensure_ascii=False)
            self.status_var.set(f"配置已导出到 {os.path.basename(filepath)}")
        except Exception as e: messagebox.showerror("导出失败", f"无法导出配置文件: {e}")

    def import_config(self):
        filepath = filedialog.askopenfilename(title="从文件导入配置...", filetypes=[("JSON files", "*.json")])
        if not filepath: return
        if not messagebox.askyesno("确认导入", "导入将覆盖当前所有设置。\n确定要继续吗？"): return
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                config_data = json.load(f)
            # 核心：用导入的数据更新内部“大脑”，然后刷新整个UI
            self.data_sources_config = config_data.get('data_sources', {})
            # 注意：由于源可能已变，我们需要重建文件配置UI
            self.rebuild_file_tab_and_refresh(config_data)
            self.status_var.set(f"已从 {os.path.basename(filepath)} 成功导入配置。")
        except Exception as e: messagebox.showerror("导入失败", f"加载配置文件时出错: {e}")

    def rebuild_file_tab_and_refresh(self, config):
        """当导入的源与当前不同时，重建UI"""
        for widget in self.tab_files.winfo_children(): widget.destroy()
        self.create_file_config_tab() # 基于新的 self.data_sources_config 重建
        self.map_source_name['values'] = list(self.data_sources_config.keys()) # 更新下拉列表
        self.refresh_ui_from_config(config)

    def on_closing(self):
        # ... (此方法无需改动，之前的版本是正确的) ...
        response = messagebox.askyesnocancel("退出确认", "您想在退出前保存更改吗？", icon=messagebox.QUESTION)
        if response is True: self.save_config(show_success=False); self.destroy()
        elif response is False: self.destroy()

    def run_processing(self):
        # ... (此方法无需改动) ...
        self.status_var.set("正在处理中..."); self.update_idletasks()
        current_config = self.collect_config_from_ui()
        sources_config = current_config['data_sources']; tasks = current_config['tasks']; mappings = current_config['column_mappings']
        for name, data in sources_config.items():
            if not data['path'] or not os.path.exists(data['path']): messagebox.showerror("文件错误", f"数据源 '{name}' 的文件路径无效！"); self.status_var.set("处理失败。"); return
        if not tasks: messagebox.showwarning("配置错误", "任务列表为空。"); self.status_var.set("准备就绪。"); return
        if not mappings: messagebox.showwarning("配置错误", "字段映射为空。"); self.status_var.set("准备就绪。"); return
        df_mappings = pd.DataFrame(mappings, columns=['OutputColumn', 'SourceName', 'SourceColumn'])
        try:
            dataframes = {}
            for name, config in sources_config.items():
                file_path = config['path']; file_type = config['type']
                if file_type == 'xlsx': df = pd.read_excel(file_path)
                elif file_type == 'csv': df = read_csv_with_chardet(file_path)
                elif file_type == 'xml': df = read_xml_to_dataframe(file_path)
                else: continue
                dataframes[name] = df
            for source_name, df in dataframes.items():
                cols_to_clean = df_mappings[df_mappings['SourceName'] == source_name]['SourceColumn']
                for col in cols_to_clean.unique():
                    if col in df.columns: df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
            results = []
            for platform, channel, keyword in tasks:
                output_row = {'平台': platform, '渠道': str(channel), '关键字': str(keyword)}
                for output_col_name in df_mappings['OutputColumn'].unique():
                    total_value = 0
                    rules = df_mappings[df_mappings['OutputColumn'] == output_col_name]
                    for _, rule in rules.iterrows():
                        source_name = rule['SourceName']; source_col = rule['SourceColumn']
                        is_platform_source = platform in source_name
                        is_backend = not ("FB" in source_name or "GG" in source_name)
                        if not (is_platform_source or is_backend): continue
                        df_current = dataframes[source_name]; search_col = sources_config[source_name]['search_col']
                        if search_col not in df_current.columns or source_col not in df_current.columns: continue
                        mask = (df_current[search_col].astype(str).str.contains(str(channel), na=False)) & (df_current[search_col].astype(str).str.contains(str(keyword), na=False))
                        total_value += df_current.loc[mask, source_col].sum()
                    output_row[output_col_name] = total_value
                results.append(output_row)
            if not results: messagebox.showinfo("处理完成", "没有生成任何结果数据。"); self.status_var.set("准备就绪。"); return
            result_df = pd.DataFrame(results)
            final_col_order = ['平台', '渠道', '关键字'] + sorted([col for col in df_mappings['OutputColumn'].unique()])
            existing_cols = [col for col in final_col_order if col in result_df.columns]
            result_df = result_df[existing_cols]
            output_file = os.path.join(self.app_path, '最终汇总结果.xlsx')
            result_df.to_excel(output_file, index=False)
            messagebox.showinfo("成功", f"处理完成！\n结果已保存到：\n{output_file}")
        except Exception as e: messagebox.showerror("处理失败", f"处理过程中发生错误：\n{e}")
        finally: self.status_var.set("准备就绪。")

# --- 启动应用程序 ---
if __name__ == "__main__":
    app = DataToolApp()
    app.mainloop()